﻿var sandScript = sandScript || {};

sandScript.tools = (function () {
    'use strict';

    var m = {};

    //#region Property Documentation

    m.constructor = function () {
        /// <field name="shortType" type="Object">Enumeration that maps abbreviated type names to full type names.</field>
    };

    //#endregion

    //#region Enums

    m.shortType = {
        s: 'string',
        n: 'number',
        b: 'boolean',
        u: 'undefined',
        l: 'null',
        o: 'object',
        d: 'date',
        f: 'function',
        a: 'array',
        r: 'regexp'
    };

    m.shortType.constructor = function () {
        /// <field name="s" type="String">Short for string.</field>
        /// <field name="n" type="String">Short for number.</field>
        /// <field name="b" type="String">Short for boolean.</field>
        /// <field name="u" type="String">Short for undefined.</field>
        /// <field name="l" type="String">Short for null.</field>
        /// <field name="o" type="String">Short for object.</field>
        /// <field name="d" type="String">Short for date.</field>
        /// <field name="f" type="String">Short for function</field>
        /// <field name="a" type="String">Short for array.</field>
        /// <field name="r" type="String">Short for regexp.</field>
    };

    //#endregion

    //#region Type Methods

    m.objectTypeOf = function (value) {
        /// <summary>Returns the object type of the given value as mixed case string.</summary>
        /// <param name="value" type="Object">Any value of any type.</param>
        /// <returns type="String" />

        // manually check for both null and undefined to avoid phantomjs strangeness when calling Object.prototype.toString
        if (value === null) {
            return 'Null';
        }

        if (value === undefined) {
            return 'Undefined';
        }

        return Object.prototype.toString.call(value).slice(8, -1);
    };

    m.typeOf = function (value) {
        /// <summary>Returns either the primitive or object type of a given value as a lower cased string. Includes number variants.</summary>
        /// <param name="value" type="Object">Any value of any type.</param>
        /// <returns type="String" />

        // avoids phantomjs strangeness when calling Object.prototype.toString with null
        if (value === null) {
            return 'null';
        }

        var type = typeof value;

        if (type === 'number') {
            if (isNaN(value)) {
                return 'nan';
            }

            if (!isFinite(value)) {
                return 'infinity';
            }
        }

        // phantom returns 'function' for typeof RegExp, so pass all functions to ensure accurate object type
        if (type === 'object' || type === 'function') {
            return m.objectTypeOf(value).toLowerCase();
        }

        return type;
    };

    m.isTypeOf = function (value, compare) {
        /// <summary>Returns true if the primitive or complex type of a given value matches the comparison type, otherwise false.</summary>
        /// <param name="value" type="Object">Any value of any type.</param>
        /// <param name="compare" type="String">The object type to compare as a lowercase string. Also accepts single character type abbreviations from the shortType enum.</param>
        /// <returns type="Boolean" />

        var type = m.typeOf(value);

        return type === (m.shortType[compare] || compare);
    };

    m.isObjectType = function (value) {
        /// <summary>Returns true if a given value is an object type and is not null, otherwise false.</summary>
        /// <param name="value" type="Object">Any value of any type.</param>
        /// <returns type="Boolean" />

        if (value !== null) {
            switch (typeof value) {
            case 'function':
            case 'object':
                return true;
            }
        }

        return false;
    };

    m.isPrimitiveType = function (value) {
        /// <summary>Returns true if a given value is a primitive value type.</summary>
        /// <param name="value" type="Object">Any value of any type.</param>
        /// <returns type="Boolean" />

        switch (typeof value) {
        case 'string':
        case 'number':
        case 'boolean':
            return true;
        }

        return false;
    };

    m.isDataDescriptor = function (descriptor) {
        /// <summary>Determines whether a property descriptor is a data or an accessor type.</summary>
        /// <param name="descriptor" type="Object">A property descriptor fetched using getOwnPropertyDescriptor.</param>
        /// <returns type="Boolean">True if the descriptor is a data type, otherwise false and therefore an accessor type.</returns>

        return typeof descriptor.writable === 'boolean';
    };

    //#endregion

    //#region Inheritance

    m.constructFrom = function (instance, constructor, parameters) {
        /// <summary>Applies constructor chaining on a given object instance.</summary>
        /// <param name="instance" type="Object">An object instance to use as the subject of constructor chaining.</param>
        /// <param name="constructor" type="Function">A constructor on the instance's prototype chain.</param>
        /// <param name="parameters" type="Array" elementType="Object">Optional array of parameters to pass to the constructor.</param>

        if (!m.isTypeOf(constructor, 'f')) {
            throw new Error('The constructor parameter must be a function.');
        }

        if (!(instance instanceof constructor)) {
            throw new Error('The given instance does not inherit from the constructor, or the constructor was used without the new keyword.');
        }

        if (m.isTypeOf(parameters, 'a')) {
            constructor.apply(instance, parameters);
        } else {
            constructor.call(instance);
        }
    };

    m.prototypeFrom = function (descendant, ancestor) {
        /// <summary>Sets up prototype chaining from the ancestor to the given descendant.</summary>
        /// <param name="descendant" type="Function">Descendant function on which to apply prototype chaining.</param>
        /// <param name="ancestor" type="Function">Ancestor function who's prototype should be inherited.</param>

        if (!m.isTypeOf(descendant, 'f')) {
            throw new Error('The descendant parameter must be a function.');
        }

        if (!m.isTypeOf(ancestor, 'f')) {
            throw new Error('The ancestor parameter must be a function.');
        }

        descendant.prototype = Object.create(ancestor.prototype);
        descendant.prototype.constructor = descendant;
    };

    //#endregion

    return m;
}());
